//******************************************************************************
//this code is protected by the GNU affero GPLv3
//author:Sylvain BERTRAND <sylvain.bertrand AT gmail dot com>
//                        <digital.ragnarok AT gmail dot com>
//******************************************************************************
#define LWL_EVDEV_C
#include "globals.h"
#undef LWL_EVDEV_C

static i ep_fd;

static u8 test_bit(l *p,u16 bit_off)
{
  u16 byte_off=bit_off>>3;
  u8 byte_bit_off=bit_off%8;
  u8 *a=(u8*)p;
  return (a[byte_off]>>byte_bit_off)&1;
}

static s8 ev_bits(s8 evdev)
{
  l r=ioctl(evdevs[evdev].fd,EVIOCGBIT(0,sizeof(evdevs[evdev].ev_bits)),
                                                  (l)&evdevs[evdev].ev_bits[0]);
  if(ISERR(r)){
    LOG_EVDEV("error(%ld):unable to get the device(%s|%d) ev bits\n",r,
                                    &evdevs[evdev].devnode[0],evdevs[evdev].fd);
    return LWL_ERR;
  }
  return LWL_OK;
}

static s8 xxx_bits(s8 evdev,u16 type,l *xxx_bits_array,
                                                 l xxx_bits_array_sz,void *desc)
{
  if(!test_bit(evdevs[evdev].ev_bits,type)) return 0;

  l r=ioctl(evdevs[evdev].fd,EVIOCGBIT(type,xxx_bits_array_sz),xxx_bits_array);
  if(ISERR(r)){
    LOG_EVDEV("error(%ld):unable to get the device(%s|%d) %s bits\n",r,
                               &evdevs[evdev].devnode[0],evdevs[evdev].fd,desc);
    return LWL_ERR;
  }
  return LWL_OK;
}

void lwl_evdevs_init(i srv_ep_fd)
{
  ep_fd=srv_ep_fd;
  s8 evdev=0;
  while(1){
    if(evdev==LINUX_EVDEVS_MAX) break;
    evdevs[evdev].fd=-1;
    ++evdev;
  }
}

//return the evdev index if successful or a negative value for error
s8 lwl_evdev_new(void *devnode,s32 seat_name)
{
  LOG_EVDEV("creating device(%s) on seat(%d)\n",devnode,seat_name);

  s8 free_evdev=0;
  while(1){
    if(free_evdev==LINUX_EVDEVS_MAX) break;
    if(evdevs[free_evdev].fd==-1) break;
    ++free_evdev;
  }

  if(free_evdev==LINUX_EVDEVS_MAX){
    LOG_EVDEV("no more room for device(%s)\n",devnode);
    free_evdev=LWL_ERR;goto exit;
  }

  strncpy(&evdevs[free_evdev].devnode[0],devnode,LWL_EVDEV_DEVNODE_SZ);

  l r;
  while(1){r=open(devnode,RDWR|NONBLOCK,0); if(r!=-EINTR) break;}
  if(ISERR(r)){
    LOG_EVDEV("unable to open the device(%s) node\n",devnode);
    free_evdev=LWL_ERR;goto exit;
  }
  evdevs[free_evdev].fd=(i)r;

  r=(l)ev_bits(free_evdev);
  if(r!=0){r=LWL_ERR;goto err_close_fd;};

  //get the bits we will need for seat configuration
  if(xxx_bits(free_evdev,EV_KEY,&evdevs[free_evdev].key_bits[0],
                                 sizeof(evdevs[free_evdev].key_bits),"key")!=0){
    r=LWL_ERR;goto err_close_fd;
  }
  if(xxx_bits(free_evdev,EV_REL,&evdevs[free_evdev].rel_bits[0],
                                 sizeof(evdevs[free_evdev].rel_bits),"rel")!=0){
    r=LWL_ERR;goto err_close_fd;
  }
  if(xxx_bits(free_evdev,EV_LED,&evdevs[free_evdev].led_bits[0],
                                 sizeof(evdevs[free_evdev].led_bits),"led")!=0){
    r=LWL_ERR;goto err_close_fd;
  }

  registry_gbl_ref(&registry[seat_name]);//the evdev has a ref on its seat
  evdevs[free_evdev].seat_name=seat_name;

  struct epoll_event ep_evt;
  memset(&ep_evt,0,sizeof(ep_evt));
  ep_evt.events=EPOLLET|EPOLLIN|EPOLLHUP;//edge triggered
  ep_evt.data._32=(u32)(CLIENTS_MAX+free_evdev);

  r=epoll_ctl(ep_fd,EPOLL_CTL_ADD,evdevs[free_evdev].fd,&ep_evt);
  if(ISERR(r)){
    PERR("fatal(%ld):lwl_evdev_new:unable to register evdev(%d) to epoll\n",r,
                                                         evdevs[free_evdev].fd);
    exit(LWL_ERR);
  }

  LOG_EVDEV("device(%s:%d) on seat(%d) created index=%d\n",devnode,
                          evdevs[free_evdev].fd,seat_name,evdevs[free_evdev].fd,
                                                                    free_evdev);
exit:
  return free_evdev;

err_close_fd:
  while(1){r=close(evdevs[free_evdev].fd); if(r!=-EINTR) break;}
  return LWL_ERR;
}

void lwl_evdev_close(struct evdev *d)
{
  LOG_EVDEV("closing device(%s:%d) on seat(%d)\n",&d->devnode[0],d->fd,
                                                                  d->seat_name);

  l r=epoll_ctl(ep_fd,EPOLL_CTL_DEL,d->fd,0);
  if(ISERR(r)){
    PERR("fatal(%ld):lwl_evdev_close:unable to delete evdev(%d) from"
                                                  " epoll(%d)\n",r,d->fd,ep_fd);
    exit(LWL_ERR);
  }

  while(1){r=close(d->fd); if(r!=-EINTR) break;}
  d->fd=-1;
  registry_gbl_unref(&registry[d->seat_name]);
}

void lwl_evdev_kill(struct evdev *d)
{
  LOG_EVDEV("closing device(%s:%d) and reconfiguring seat(%d)\n",&d->devnode[0],
                                                            d->fd,d->seat_name);

  u32 evdev_seat_name=d->seat_name;
  lwl_evdev_close(d);
  //only reconfigure the seat if it is still around
  if(registry[evdev_seat_name].itf==WL_SEAT) seat_input_change(evdev_seat_name);
}

void lwl_evdev_remove(void *devnode)
{
  LOG_EVDEV("removing device(%s)\n",devnode);

  s8 evdev=0;
  while(1){
    if(evdev==LINUX_EVDEVS_MAX){
      LOG_EVDEV("device(%s) not found, unable to remove\n",devnode);
      return;
    }
    if(evdevs[evdev].fd!=-1)
      if(!strncmp(&evdevs[evdev].devnode[0],devnode,LWL_EVDEV_DEVNODE_SZ))
        break;
    ++evdev;
  }

  LOG_EVDEV("device(%s:%d) to remove found on index=%d\n",devnode,
                                                        evdevs[evdev].fd,evdev);

  lwl_evdev_kill(&(evdevs[evdev]));
}

//the following functions decide if a evdev device brings pointer features
//or keyboard features (features can be partial as they can be split over
//several event devices)
u8 lwl_evdev_has_pointer(struct evdev *d)
{
  //one button is enough to provide pointer features
  if(test_bit(&d->ev_bits[0],EV_KEY)){
    u16 bit=BTN_MISC;
    while(1){
      if(bit==KEY_OK) break;
      if(test_bit(&d->key_bits[0],bit)){
        LOG_EVDEV("device(%s:%d) has button pointer feature(s)\n",
                                                          &d->devnode[0],d->fd);
        return 1;
      }
      ++bit;
    }
  }

  //if we have rel bits, we want x *and* y
  if(test_bit(&d->ev_bits[0],EV_REL))
    if(test_bit(&d->rel_bits[0],REL_X)&&test_bit(&d->rel_bits[0],REL_Y)){
      LOG_EVDEV("device(%s:%d) has relative pointer feature(s)\n",
                                                          &d->devnode[0],d->fd);
      return 1;
    }
  LOG_EVDEV("device(%s:%d) has no pointer feature\n",&d->devnode[0],d->fd);
  return 0;
}

u8 lwl_evdev_has_keyboard(struct evdev *d)
{
  //the key bits have keys *and* buttons
  if(test_bit(&d->ev_bits[0],EV_KEY)){
    u16 bit=KEY_ESC;
    while(1){
      if(bit==KEY_MAX) break;
      //exclude buttons
      if(BTN_MISC<=bit&&bit<KEY_OK) continue;
      //one key is enough
      if(test_bit(&d->key_bits[0],bit)){
        LOG_EVDEV("device(%s:%d) has key keyboard feature(s)\n",&d->devnode[0],
                                                                         d->fd);
        return 1;
      }
      ++bit;
    }
  }

  //to have led is enough to be a keyboard
  if(test_bit(&d->ev_bits[0],EV_LED)){
    LOG_EVDEV("device(%s:%d) has led keyboard feature(s)\n",&d->devnode[0],
                                                                         d->fd);
    return 1;
  }
  LOG_EVDEV("device(%s:%d) has no keyboard feature\n",&d->devnode[0],d->fd);
  return 0;
}
